#include "atom.h"
#include "bytecode.h"
#include "configuration.h"
#include "generalizer.h"
#include "reference.h"
#include "tree.h"
#include "tuple.h"

pNode Generalizer::generalize (Node &node) {
  Node &finalNode = *node.final ();

  if(typeid(finalNode) == typeid(Atom)) {
    Atom *atom = &static_cast<Atom&> (finalNode);
    std::string &name = atom->name;

    if(isVariable(name)) {
      if(name == anythingVariable)
        return new Reference;

      int nVariables = names.size ();
      for(int i = 0; i < nVariables; i++)
        if(names[i] == atom)
          return unbindeds[i];

      // No match; a new variable
      names.push_back (atom);
      unbindeds.push_back (new Reference);
      return unbindeds.back ();
    } else
      return &finalNode;

  } else if(typeid(finalNode) == typeid(Tuple)) {
    Tuple &tuple = static_cast<Tuple&> (finalNode);

    for(std::vector<pNode>::iterator p = tuple.nodes.begin (); p
        < tuple.nodes.end (); p++) {
      pNode generalized (generalize (**p));

      if(p->node != generalized.node) { // Copy on write
        Tuple *ret = new Tuple;
        ret->nodes.insert (ret->nodes.begin (), tuple.nodes.begin (), p);
        ret->nodes.push_back (generalized);
        for(p++; p < tuple.nodes.end (); p++)
          ret->nodes.push_back (generalize (**p));
        return ret;
      }
    }

    return &tuple;

  } else if(typeid(finalNode) == typeid(Tree)) {
    Tree &tree = static_cast<Tree&> (finalNode);
    pNode left = generalize (*tree.left);
    pNode right = generalize (*tree.right);
    if(left == tree.left && right == tree.right) // No generalization
      return &finalNode;
    else
      return new Tree (tree.ops, left, right);

  } else
    return &node; // Let's not think
}

std::string Generalizer::dump () {
  std::string ret;

  int nVariables = names.size ();
  for(int i = 0; i < nVariables; i++) {
    if(i)
      ret += ", ";
    ret += names[i]->name;
    ret += " = ";
    ret += unbindeds[i].dump ();
  }

  return ret;
}

pNode generalize (Node &node) {
  Generalizer generalizer;
  return generalizer.generalize (node);
}
